﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Perforce.P4;
using static System.Windows.Application;

namespace Red.Core.SourceControl
{
    public class Monitor
    {
        #region Private Data

        [Flags]
        enum EventType
        {
            OperationStarted    = 1 << 0,
            OperationFinished   = 1 << 1,
            ChangelistAdded     = 1 << 2,
            ChangelistDeleted   = 1 << 3,
            ChangelistSubmitted = 1 << 4,
            ChangelistUpdated   = 1 << 5,
            Error               = 1 << 6,
            NotSignedIn         = 1 << 7
        }

        private Perforce.Mediator _sourceControl;

        private EventType _registered;

        #endregion Private Data

        #region Public Properties

        public Perforce.Mediator SourceControl
        {
            get { return _sourceControl; }
            set
            {
                UnregisterListeners();
                _sourceControl = value;
                RegisterListeners();
            }
        }

        public ICollection<string> Files { get; private set; } = new List<string>();

        public bool OperationStartedHasListeners => OperationStartedInternal?.GetInvocationList().Length > 0;
        public bool OperationFinishedHasListeners => OperationFinishedInternal?.GetInvocationList().Length > 0;
        public bool ChangelistAddedHasListeners => ChangelistAddedInternal?.GetInvocationList().Length > 0;
        public bool ChangelistDeletedHasListeners => ChangelistDeletedInternal?.GetInvocationList().Length > 0;
        public bool ChangelistSubmittedHasListeners => ChangelistSubmittedInternal?.GetInvocationList().Length > 0;
        public bool ChangelistUpdatedHasListeners => ChangelistUpdatedInternal?.GetInvocationList().Length > 0;
        public bool ErrorHasListeners => ErrorInternal?.GetInvocationList().Length > 0;
        public bool NotSignedInHasListeners => NotSignedInInternal?.GetInvocationList().Length > 0;

        private bool IsRegistered( EventType type )
        {
            return ( _registered & type ) == type;
        }

        #endregion Public Properties

        #region Public Methods

        public void Clear()
        {
            Files.Clear();
            UnregisterListeners();
        }

        private void RegisterOperationStartedListeners()
        {
            if ( SourceControl != null && OperationStartedHasListeners && !IsRegistered( EventType.OperationStarted ) )
            {
                SourceControl.CheckoutStarted += SourceControl_OperationStarted;
                SourceControl.GetLatestStarted += SourceControl_OperationStarted;
                SourceControl.RevertStarted += SourceControl_OperationStarted;
                SourceControl.AddStarted += SourceControl_OperationStarted;

                _registered |= EventType.OperationStarted;
            }
        }

        private void UnregisterOperationStartedListeners()
        {
            if ( SourceControl != null && IsRegistered( EventType.OperationStarted ) )
            {
                SourceControl.CheckoutStarted -= SourceControl_OperationStarted;
                SourceControl.GetLatestStarted -= SourceControl_OperationStarted;
                SourceControl.RevertStarted -= SourceControl_OperationStarted;
                SourceControl.AddStarted -= SourceControl_OperationStarted;

                _registered &= ~EventType.OperationStarted;
            }
        }

        private void RegisterOperationFinishedListeners()
        {
            if ( SourceControl != null && OperationFinishedHasListeners && !IsRegistered( EventType.OperationFinished ) )
            {
                SourceControl.CheckoutFinished += SourceControl_OperationFinished;
                SourceControl.GetLatestFinished += SourceControl_OperationFinished;
                SourceControl.RevertFinished += SourceControl_OperationFinished;
                SourceControl.AddFinished += SourceControl_OperationFinished;

                _registered |= EventType.OperationFinished;
            }
        }

        private void UnregisterOperationFinishedListeners()
        {
            if ( SourceControl != null && IsRegistered( EventType.OperationFinished ) )
            {
                SourceControl.CheckoutFinished -= SourceControl_OperationFinished;
                SourceControl.GetLatestFinished -= SourceControl_OperationFinished;
                SourceControl.RevertFinished -= SourceControl_OperationFinished;
                SourceControl.AddFinished -= SourceControl_OperationFinished;

                _registered &= ~EventType.OperationFinished;
            }
        }

        private void RegisterChangelistAddedListeners()
        {
            if ( SourceControl != null && ChangelistAddedHasListeners && !IsRegistered( EventType.ChangelistAdded ) )
            {
                SourceControl.ChangelistAdded += SourceControl_ChangelistAdded;

                _registered |= EventType.ChangelistAdded;
            }
        }

        private void UnregisterChangelistAddedListeners()
        {
            if ( SourceControl != null && IsRegistered( EventType.ChangelistAdded ) )
            {
                SourceControl.ChangelistAdded -= SourceControl_ChangelistAdded;

                _registered &= ~EventType.ChangelistAdded;
            }
        }

        private void RegisterChangelistDeletedListeners()
        {
            if ( SourceControl != null && ChangelistDeletedHasListeners && !IsRegistered( EventType.ChangelistDeleted ) )
            {
                SourceControl.ChangelistDeleted += SourceControl_ChangelistDeleted;

                _registered |= EventType.ChangelistDeleted;
            }
        }

        private void UnregisterChangelistDeletedListeners()
        {
            if ( SourceControl != null && IsRegistered( EventType.ChangelistDeleted ) )
            {
                SourceControl.ChangelistDeleted -= SourceControl_ChangelistDeleted;

                _registered &= ~EventType.ChangelistDeleted;
            }
        }

        private void RegisterChangelistSubmittedListeners()
        {
            if ( SourceControl != null && ChangelistSubmittedHasListeners && !IsRegistered( EventType.ChangelistSubmitted ) )
            {
                SourceControl.ChangelistSubmitted += SourceControl_ChangelistSubmitted;

                _registered |= EventType.ChangelistSubmitted;
            }
        }

        private void UnregisterChangelistSubmittedListeners()
        {
            if ( SourceControl != null && IsRegistered( EventType.ChangelistSubmitted ) )
            {
                SourceControl.ChangelistSubmitted -= SourceControl_ChangelistSubmitted;

                _registered &= ~EventType.ChangelistSubmitted;
            }
        }

        private void RegisterChangelistUpdatedListeners()
        {
            if ( SourceControl != null && ChangelistUpdatedHasListeners && !IsRegistered( EventType.ChangelistUpdated ) )
            {
                SourceControl.ChangelistUpdated += SourceControl_ChangelistUpdated;

                _registered |= EventType.ChangelistUpdated;
            }
        }

        private void UnregisterChangelistUpdatedListeners()
        {
            if ( SourceControl != null && IsRegistered( EventType.ChangelistUpdated ) )
            {
                SourceControl.ChangelistUpdated -= SourceControl_ChangelistUpdated;

                _registered &= ~EventType.ChangelistUpdated;
            }
        }

        private void RegisterErrorListeners()
        {
            if ( SourceControl != null && ErrorHasListeners && !IsRegistered( EventType.Error ) )
            {
                SourceControl.Error += SourceControl_Error;

                _registered |= EventType.Error;
            }
        }

        private void UnregisterErrorListeners()
        {
            if ( SourceControl != null && IsRegistered( EventType.Error ) )
            {
                SourceControl.Error -= SourceControl_Error;

                _registered &= ~EventType.Error;
            }
        }

        private void RegisterNotSignedInListeners()
        {
            if ( SourceControl != null && NotSignedInHasListeners && !IsRegistered( EventType.NotSignedIn ) )
            {
                SourceControl.NotSignedIn += SourceControl_NotSignedIn;

                _registered |= EventType.NotSignedIn;
            }
        }

        private void UnregisterNotSignedInListeners()
        {
            if ( SourceControl != null && IsRegistered( EventType.NotSignedIn ) )
            {
                SourceControl.NotSignedIn -= SourceControl_NotSignedIn;

                _registered &= ~EventType.NotSignedIn;
            }
        }


        public void RegisterListeners()
        {
            RegisterOperationStartedListeners();
            RegisterOperationFinishedListeners();
            RegisterChangelistAddedListeners();
            RegisterChangelistDeletedListeners();
            RegisterChangelistSubmittedListeners();
            RegisterChangelistUpdatedListeners();
            RegisterErrorListeners();
            RegisterNotSignedInListeners();
        }

        public void UnregisterListeners()
        {
            UnregisterOperationStartedListeners();
            UnregisterOperationFinishedListeners();
            UnregisterChangelistAddedListeners();
            UnregisterChangelistDeletedListeners();
            UnregisterChangelistSubmittedListeners();
            UnregisterChangelistUpdatedListeners();
            UnregisterErrorListeners();
            UnregisterNotSignedInListeners();
        }

        #endregion Public Methods

        #region Event Handlers

        private void SourceControl_OperationStarted( IEnumerable<string> filePaths )
        {
            foreach ( string path in filePaths )
            {
                if ( Files.Contains( path ) )
                {
                    Current.Dispatcher.BeginInvoke( (Action)( () => { OperationStartedInternal?.Invoke( path ); } ) );
                }
            }
        }

        private void SourceControl_OperationFinished( IEnumerable<string> requested, IEnumerable<string> modified )
        {
            Task.Run( () =>
            {
                IEnumerable<FileMetaData> metadata = SourceControl.GetFileMetaData( requested );

                // Iterate over all the files (not just those that perforce told us were checked out)
                foreach ( string path in requested )
                {
                    if ( Files.Contains( path ) )
                    {
                        FileMetaData data = metadata.SingleOrDefault( item => item.LocalPath.Path == path );
                        Current.Dispatcher.BeginInvoke( (Action)( () => { OperationFinishedInternal?.Invoke( path, data ); } ) );
                    }
                }
            } );
        }

        private void SourceControl_ChangelistAdded( int changelistId )
        {
            Current.Dispatcher.BeginInvoke( (Action)( () => { ChangelistAddedInternal?.Invoke( changelistId ); } ) );
        }

        private void SourceControl_ChangelistDeleted( int changelistId )
        {
            Current.Dispatcher.BeginInvoke( (Action)( () => { ChangelistDeletedInternal?.Invoke( changelistId ); } ) );
        }

        private void SourceControl_ChangelistSubmitted( int changelistId )
        {
            Current.Dispatcher.BeginInvoke( (Action)( () => { ChangelistSubmittedInternal?.Invoke( changelistId ); } ) );
        }

        private void SourceControl_ChangelistUpdated( int changelistId )
        {
            Current.Dispatcher.BeginInvoke( (Action)( () => { ChangelistUpdatedInternal?.Invoke( changelistId ); } ) );
        }

        private void SourceControl_Error( string message )
        {
            Current.Dispatcher.BeginInvoke( (Action)( () => { ErrorInternal?.Invoke( message ); } ) );
        }

        private void SourceControl_NotSignedIn( string username )
        {
            Current.Dispatcher.BeginInvoke( (Action)( () => {  NotSignedInInternal?.Invoke( username ); } ) );
        }

        #endregion Event Handlers

        #region Events

        public delegate void OperationStartedDelegate( string file );
        public delegate void OperationFinishedDelegate( string file, FileMetaData metadata );
        public delegate void ChangelistEventHandler( int changelistId );
        public delegate void ErrorDelegate( string message );
        public delegate void NotSignedInDelegate( string username );

        private event OperationStartedDelegate OperationStartedInternal;
        private event OperationFinishedDelegate OperationFinishedInternal;
        private event ChangelistEventHandler ChangelistAddedInternal;
        private event ChangelistEventHandler ChangelistDeletedInternal;
        private event ChangelistEventHandler ChangelistSubmittedInternal;
        private event ChangelistEventHandler ChangelistUpdatedInternal;
        private event ErrorDelegate ErrorInternal;
        private event NotSignedInDelegate NotSignedInInternal;

        public event OperationStartedDelegate OperationStarted
        {
            add
            {
                OperationStartedInternal += value;
                RegisterOperationStartedListeners();
            }

            remove
            {
                OperationStartedInternal -= value;

                if( !OperationStartedHasListeners )
                {
                    UnregisterOperationFinishedListeners();
                }
            }
        }

        public event OperationFinishedDelegate OperationFinished
        {
            add
            {
                OperationFinishedInternal += value;
                RegisterOperationFinishedListeners();
            }

            remove
            {
                OperationFinishedInternal -= value;

                if( !OperationFinishedHasListeners )
                {
                    UnregisterOperationFinishedListeners();
                }
            }
        }

        public event ChangelistEventHandler ChangelistAdded
        {
            add
            {
                ChangelistAddedInternal += value;
                RegisterChangelistAddedListeners();
            }

            remove
            {
                ChangelistAddedInternal -= value;

                if( !ChangelistAddedHasListeners )
                {
                    UnregisterChangelistAddedListeners();
                }
            }
        }

        public event ChangelistEventHandler ChangelistDeleted
        {
            add
            {
                ChangelistDeletedInternal += value;
                RegisterChangelistDeletedListeners();
            }

            remove
            {
                ChangelistDeletedInternal -= value;

                if( !ChangelistDeletedHasListeners )
                {
                    UnregisterChangelistDeletedListeners();
                }
            }
        }

        public event ChangelistEventHandler ChangelistSubmitted
        {
            add
            {
                ChangelistSubmittedInternal += value;
                RegisterChangelistSubmittedListeners();
            }

            remove
            {
                ChangelistSubmittedInternal -= value;

                if( !ChangelistSubmittedHasListeners )
                {
                    UnregisterChangelistSubmittedListeners();
                }
            }
        }

        public event ChangelistEventHandler ChangelistUpdated
        {
            add
            {
                ChangelistUpdatedInternal += value;
                RegisterChangelistUpdatedListeners();
            }

            remove
            {
                ChangelistUpdatedInternal -= value;

                if( !ChangelistUpdatedHasListeners )
                {
                    UnregisterChangelistUpdatedListeners();
                }
            }
        }

        /// <summary>
        /// Convenience catch-all changelist events
        /// </summary>
        public event ChangelistEventHandler Changelist
        {
            add
            {
                ChangelistAdded += value;
                ChangelistDeleted += value;
                ChangelistSubmitted += value;
                ChangelistUpdated += value;
            }
            remove
            {
                ChangelistAdded -= value;
                ChangelistDeleted -= value;
                ChangelistSubmitted -= value;
                ChangelistUpdated -= value;
            }
        }

        public event ErrorDelegate Error
        {
            add
            {
                ErrorInternal += value;
                RegisterErrorListeners();
            }

            remove
            {
                ErrorInternal -= value;

                if( !ErrorHasListeners )
                {
                    UnregisterErrorListeners();
                }
            }
        }

        public event NotSignedInDelegate NotSignedIn
        {
            add
            {
                NotSignedInInternal += value;
                RegisterNotSignedInListeners();
            }

            remove
            {
                NotSignedInInternal -= value;

                if( !NotSignedInHasListeners )
                {
                    UnregisterNotSignedInListeners();
                }
            }
        }

        #endregion Events
    }
}
